﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Media;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Drawing.Imaging;

namespace Converter
{
    /// <summary>
    /// main class
    /// </summary>
    class Program
    {
        private static string _TargetDirectory = "C:\\temp\\SilverZoom\\processed";
        private static string _ImageDirectory = "C:\\temp\\SilverZoom\\todo";
        private static int _Compression = 100;
        private static int _MaxZoom = 2;
        private static int _TileSizeX, _TileSizeY = 0;
        
        /// <summary>
        /// main procedure call, checks the incoming arguments at program start
        /// </summary>
        /// <param name="args">array of fiels or folders</param>
        static void Main(string[] args)
        {
            // creates an instance of the image collection
            ImageCollection Collection = new ImageCollection();            
            
            // loop through all the incoming arguments
            for(int i = 0; i < args.Length; i++)
            {
                //Console.WriteLine(args[0]);
                if (args[i].StartsWith("/") || args[i].StartsWith("-"))
                {
                    ParseCommand(args[i] + " " + args[i+1], Collection);
                    i++;
                }
                else if (Directory.Exists(args[i]))
                {
                    // if a directory is give, loop through the content
                    ParseDirectoryContent(args[i], Collection);
                }
                else if (File.Exists(args[i]))
                {
                    // if an image is given just parse it
                    ParseImage(args[i], Collection);
                }
            }

            // now that all files should have been parsed and loaded into the collection, begin working with the items
            if (Collection.Images.Count > 0)
            {
                Console.WriteLine("There are " + Collection.Images.Count.ToString() + " Images in the Collection");

                int count = Collection.Images.Count;
                int current = 0;

                int active = 0;
                int complete = 0;
                ThreadPool.GetAvailableThreads(out active, out complete);

                int max = Environment.ProcessorCount;
                bool bla = ThreadPool.SetMaxThreads(active, complete);

                if (count != 0)
                {
                    Console.WriteLine("Starting the image converting process...");


                    foreach (Image image in Collection.Images)
                    {
                        current++;
                        image.Number = current;
                        image.Count = count;
                        //ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), image);
                        Console.Write("processing image " + current.ToString("000") + "/" + count.ToString("000"));
                        ProcessImage(image);
                        Console.WriteLine(" --- done");
                    }
                }

                ThreadPool.GetAvailableThreads(out active, out complete);

                while ((complete - active) != 0)
                //while (active != Environment.ProcessorCount)
                {
                    Thread.Sleep(1000);
                    ThreadPool.GetAvailableThreads(out active, out complete);
                }
            }
            Console.ReadLine();
        }
        
        static void ThreadProc(Object stateInfo)
        {
            Image image = (Image)stateInfo;
            Console.WriteLine("Thread processes image " + image.Number.ToString("000") + " of " + image.Count.ToString("000"));
            ProcessImage(image);
            Console.WriteLine("Thread finished processing image " + image.Number.ToString("000") + " of " + image.Count.ToString("000"));
        }

        private static string TargetDirectory
        {
            get
            {
                return _TargetDirectory;
            }
            set
            {
                _TargetDirectory = value;
            }
        }

        private static string ImageDirectory
        {
            get
            {
                return _ImageDirectory;
            }
            set
            {
                _ImageDirectory = value;
            }
        }
        
        private static int Compression
        {
            get
            {
                return _Compression;
            }
            set
            {
                _Compression = Convert.ToInt32(value);
            }
        }

        private static int MaxZoom
        {
            get
            {
                return _MaxZoom;
            }
            set
            {
                _MaxZoom = Convert.ToInt32(value);
            }
        }

        private static int TileSizeX
        {
            get
            {
                return _TileSizeX;
            }
            set
            {
                _TileSizeX = Convert.ToInt32(value);
            }
        }

        private static int TileSizeY
        {
            get
            {
                return _TileSizeY;
            }
            set
            {
                _TileSizeY = Convert.ToInt32(value);
            }
        }

        private static void ParseCommand(string argument, ImageCollection collection)
        {
            int pos = argument.IndexOf(" ");
            string command = argument.Substring(1,pos-1).ToLower();
            string value = argument.Substring(pos + 1).Replace("\"","").Replace("'","");
                
            switch(command)
            {
                case "target":
                if (!Directory.Exists(value))
                    Directory.CreateDirectory(value);
                TargetDirectory = value;
                break;
                case "folder":
                    string[] split = value.Split(',');
                    foreach(string dir in split)
                        if (Directory.Exists(dir))
                            ParseDirectoryContent(dir, collection);
                        else
                            Console.WriteLine("Directory not found : " + dir);
                    break;
                case "file":
                    split = value.Split(',');
                    foreach(string file in split)
                        if (File.Exists(file))
                            ParseImage(file, collection);
                        else
                            Console.WriteLine("File not found : " + file);
                    break;
                case "compression":
                    Compression = Int32.Parse(value);
                    break;
                case "maxzoom":
                    MaxZoom = Int32.Parse(value);
                    break;
                case "?": 
                case "help": 
                default:
                    Console.Clear();    
                    Console.WriteLine("NeoGeo New Media GmbH");
                    Console.WriteLine("SilverZoom Image Converter");
                    Console.WriteLine("----------------------------------------------");
                    Console.WriteLine("use commandline arguments as follows...");
                    Console.WriteLine("'-target'       specifies the destination folder for the processed image tiles");
                    Console.WriteLine("'-folder'       specifies a folder where the to be processed images are");
                    Console.WriteLine("'-file'         specifies a single file which shall be processed");
                    Console.WriteLine("'-compression'  specifies the quality of the resulting images");
                    Console.WriteLine("'-maxzoom'      specifies the maximum possible depth of zoom, 1 is the lowest, 3 is the optimum");
                    break;                     
            }
        }

        /// <summary>
        /// processes the current image and creates the necessary tiles calculated by the size of the x and y values
        /// </summary>
        /// <param name="image">image object</param>
        private static void ProcessImage(Image image)
        {
            try
            {
                // retrieve the image dimensions
                BitmapImage bmpImage = new BitmapImage(image.UriImagePath);
                BitmapSource bmpSource = bmpImage;

                int height = bmpSource.PixelHeight;
                int width = bmpSource.PixelWidth;

                if (image.isMain)
                {
                    int depth = 0;

                    // calculate the possible depth of zooming
                    if (width >= height)
                    {
                        int _width = width;
                        while (_width >= 400)
                        {
                            _width = _width / 3;
                            depth++;
                        }
                    }
                    else
                    {
                        int _height = height;
                        while (_height >= 300)
                        {
                            _height = _height / 3;
                            depth++;
                        }
                    }
                    
                    // if there's a batchcommand zoom use it instead, but only if possible
                    if (MaxZoom >= depth) MaxZoom = depth;

                    // set the size for the tile images
                    TileSizeX = Convert.ToInt32(width / Math.Pow(3, MaxZoom));
                    TileSizeY = Convert.ToInt32(height / Math.Pow(3, MaxZoom));

                    // create a preview and overview image of the current large ressource
                    GeneratePreviewImages(image, width, height);
                }   

                if (MaxZoom != 0)
                {
                    BuildJSDefinition(image, string.Empty, 1, width, height);

                    // process the tiles for the current image
                    TileImage(image, string.Empty, 1, 0, 0, width, height);                    
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// creates the preview image of the current large image
        /// </summary>
        /// <param name="image">the image object</param>
        /// <param name="width">the current max width</param>
        /// <param name="height">the current max height</param>
        private static void GeneratePreviewImages(Image image, int width, int height)
        {
            // calculate the coefficient for scaling the image down to the viewport size
            // retrieve dimensions for a preview image and an overview image
            int _width, _smallwidth = 0;
            int _height, _smallheight = 0;
            double factor = Convert.ToDouble(width) / Convert.ToDouble(height);

            // landscape
            if (width >= height)
            {
                _width = 400;
                _smallwidth = 100;
                _height = Convert.ToInt32(_width / factor);
                _smallheight = Convert.ToInt32(_smallwidth / factor);
            }
            // portrait
            else
            {
                _height = 300;
                _smallheight = 75;
                _width = Convert.ToInt32(_height * factor);
                _smallwidth = Convert.ToInt32(_smallheight * factor);
            }

            // get the sourceimage
            System.Drawing.Bitmap source = new Bitmap(image.ImagePath);            
            
            // directory where the processed image are to be put
            string dir = TargetDirectory + "\\" + image.Name;
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);
            
            // filename of the preview image
            string previewName = dir + "\\" + image.Name + "_preview.jpg";
            string overviewName = dir + "\\" + image.Name + "_overview.jpg";

            // create two new bitmap objects and copy the source image into the resized bitmaps
            System.Drawing.Bitmap preview = new Bitmap(source, _width, _height);
            System.Drawing.Bitmap overview = new Bitmap(source, _smallwidth, _smallheight);

            // create a new jpeg encoding for quality/compression purposes
            EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, Compression); 
            ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
            EncoderParameters encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = qualityParam;

            // save and dispose the images
            preview.Save(previewName, jpegCodec, encoderParams);
            overview.Save(overviewName, jpegCodec, encoderParams);
            preview.Dispose();
            overview.Dispose();
            source.Dispose();
        }

        /// <summary>
        /// returns the ImageCodecInfo for the string mime type
        /// </summary>
        /// <param name="mimeType">string containing the image mime type</param>
        /// <returns>an obejct of ImageCodecInfo for the corrosponding mime type</returns>
        private static ImageCodecInfo GetEncoderInfo(string mimeType)
        {
            // Get image codecs for all image formats 
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

            // Find the correct image codec 
            for (int i = 0; i < codecs.Length; i++)
                if (codecs[i].MimeType == mimeType)
                    return codecs[i];
            return null;
        } 
        
        /// <summary>
        /// creates the tiles for the current image object
        /// </summary>
        /// <param name="image">the image object</param>
        /// <param name="width">the current max width</param>
        /// <param name="height">the current max height</param>
        private static void TileImage(Image image, string dirImage, int zoom, int left, int top, int width, int height)
        {            
            // calculate the tile dimensions
            int widthPart = Convert.ToInt32(width / Math.Pow(3, zoom));
            int heightPart = Convert.ToInt32(height / Math.Pow(3, zoom));
            
            System.Drawing.Bitmap source = new Bitmap(image.ImagePath);

            // loop through the x-axis
            for(int x = 0; x < 3; x++)
            {
                // loop through the y-axis
                for(int y = 0; y < 3; y++)
                {
                    // create a sub directory for the collection of tiles to be created
                    string tileDir = string.Empty;
                    if (dirImage == string.Empty)
                        tileDir = TargetDirectory + "\\" + image.Name;
                    else
                        tileDir = dirImage;
                    if (!Directory.Exists(tileDir))
                        Directory.CreateDirectory(tileDir);

                    // build a nice name for the tile and zoom
                    string name = tileDir + "\\" + image.Name + "_" + x.ToString() + "_" + y.ToString() + ".jpg";
                    // create a cropped image from the original image object and save it to the created directory
                    Rectangle rect = new Rectangle(0, 0, widthPart, heightPart);
                    System.Drawing.Bitmap tile = new Bitmap((int)rect.Width, (int)rect.Height);
                    Graphics gfx = Graphics.FromImage(tile);
                    gfx.DrawImage(source, rect, (float)(left + x * widthPart), (float)(top + y * heightPart), widthPart, heightPart, GraphicsUnit.Pixel);
                    gfx.Dispose();

                    System.Drawing.Bitmap resize = new Bitmap(tile, TileSizeX, TileSizeY);

                    // create a new jpeg encoding for quality/compression purposes
                    EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, Compression);
                    ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
                    EncoderParameters encoderParams = new EncoderParameters(1);
                    encoderParams.Param[0] = qualityParam;

                    // save and dispose the image
                    resize.Save(name, jpegCodec, encoderParams);
                    resize.Dispose();
                    tile.Dispose();
                    
                    // for the newly created file call the same method again
                    // if the image is large enough, it will be tiled again
                    if (zoom < MaxZoom)
                    {
                        tileDir = tileDir + "\\" + image.Name + "_" + x.ToString() + "_" + y.ToString();
                        TileImage(image, tileDir, zoom + 1, left + x * widthPart, top + y * heightPart, width, height);
                    }
                }
            }         
        }

        /// <summary>
        /// used to loop through the content of a given directory and parsing the images within
        /// </summary>
        /// <param name="path">directory path</param>
        private static void ParseDirectoryContent(string path, ImageCollection Collection)
        {
            // gets all files in the current directory, no subfolders are searched
            string[] files = Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly);

            if (files.Length != 0)
                foreach (string file in files)
                    ParseImage(file, Collection);
            else
                Console.WriteLine("Notice :: Directory '" + path + "' is empty!");
        }

        /// <summary>
        /// used to parse a single image. following checking the dimensions, creating a bitmapsource, cropping it out
        /// </summary>
        /// <param name="path">path to the image file</param>
        private static void ParseImage(string path, ImageCollection Collection)
        {
            if (isImage(path))
            {
                Image add = new Image(path);
                add.isMain = true;
                Collection.Images.Add(add);
            }
        }

        /// <summary>
        /// checks whether the given path contains a bitmap image at the end of the line
        /// </summary>
        /// <param name="path">path to the image file</param>
        /// <returns>true/false</returns>
        private static bool isImage(string path)
        {
            if (File.Exists(path))
            {
                string extension = FileExtension(path);

                if (extension == "jpg" || extension == "png" || extension == "bmp" || extension == "tif")
                    return true;
                else
                    return false;
            }
            return false;
        }

        /// <summary>
        /// retrieves the extension of the given file
        /// </summary>
        /// <param name="path">path to the file</param>
        /// <returns>extension as string without the dot</returns>
        private static string FileExtension(string path)
        {
            string[] split = path.Split('.');
            return split[split.Length - 1].ToLower();
        }

        private static void BuildJSDefinition(Image image, string dirImage, int zoom, int width, int height)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("var image_dir = 'images/" + image.Name + "/';");
            sb.AppendLine("var image_width = " + width + ";");
            sb.AppendLine("var image_height = " + height + ";");
            sb.AppendLine("var tile_width = " + TileSizeX + ";");
            sb.AppendLine("var tile_height = " + TileSizeY + ";");
            sb.AppendLine("var image_zoom = " + MaxZoom + ";");
            sb.AppendLine("var image_name = '" + image.Name + "';");
            sb.AppendLine();

            for (int x = 0; x < 3; x++)
            {
                for (int y = 0; y < 3; y++)
                {
                    sb.AppendLine("var " + image.Name + "_" + x + "_" + y + " = new Array(" + MaxZoom + ");");
                    sb.AppendLine(image.Name + "_" + x + "_" + y + "[0] = '" + image.Name + "_" + x + "_" + y + ".jpg';");
                    for (int z = 1; z < MaxZoom; z++)
                    {
                        sb.AppendLine(image.Name + "_" + x + "_" + y + "[" + z + "] = new Array(" + Math.Pow(9, (z)) + ");");
                    }
                    sb.AppendLine();
                }
            }
            sb.AppendLine(LoopThroughGrid(image));

            string filePath = TargetDirectory + "\\" + image.Name + "\\object.js";
            File.WriteAllText(filePath, sb.ToString());
        }

        private static string LoopThroughGrid(Image image)
        {
            StringBuilder sb = new StringBuilder();
            for (int x = 0; x < 3; x++)
            {
                for (int y = 0; y < 3; y++)
                {
                    for (int z = 1; z < MaxZoom; z++)
                    {
                        string arrayName = image.Name + "_" + x + "_" + y + "[" + z + "]";
                        string folder = string.Empty;
                        for (int i = 0; i < z; i++)
                            folder = folder + image.Name + "_" + x + "_" +y + "/";
                        sb.AppendLine(LoopThroughFiles(image, arrayName, folder));
                    }
                }
            }
            return sb.ToString();
        }

        private static string LoopThroughFiles(Image image, string arrayName, string folder)
        {
            StringBuilder sb = new StringBuilder();
            int count = 0;

            for (int x = 0; x < 3; x++)
            {
                for (int y = 0; y < 3; y++)
                {
                    sb.AppendLine(arrayName + "[" + count + "] = '" + folder + image.Name + "_" + x + "_" + y + ".jpg';");
                    count++;
                }
            }
            return sb.ToString();
        }
    }

    /// <summary>
    /// simple storage class for multiple image objects
    /// </summary>
    public class ImageCollection
    {
        // generic list for all image objects
        public List<Image> Images = new List<Image>();
    }
    
    /// <summary>
    /// image class, storing all the information about the single files
    /// </summary>
    public class Image
    {
        public bool isMain = false;
        public int Number = 0;
        public int Count = 0;
        public int Zoom = 0;
        public string FileName = string.Empty;
        public string Name = string.Empty;
        public string ImagePath = string.Empty;
        public string Extension = string.Empty;
        public string Directory = string.Empty;
        public long FileSize = 0;
        public Uri UriImagePath = null;
        
        /// <summary>
        /// constructor for a new image, retrieves everything out of the path
        /// </summary>
        /// <param name="imagePath">path to the current image</param>
        public Image(string imagePath)
        {
            if (File.Exists(imagePath))
            {                
                UriImagePath = new Uri(imagePath);
                
                FileInfo fi = new FileInfo(imagePath);
                Extension = fi.Extension;
                ImagePath = fi.FullName;
                Directory = fi.DirectoryName;
                FileName = fi.Name;
                Name = FileName.Remove(FileName.Length - 4);
                FileSize = fi.Length;
            }
        }
    }
}
